namespace cxxcall
{

#define I_(x)  this->iargs[x]

bool av_data::call ()
{
  if (this->error ())
    return (false);

  register uintptr_t *sp __asm__ ("sp");
  register uintptr_t iret2reg __asm__ ("x1");

  ptrdiff_t arg_len = this->curp - this->args;
  if (arg_len > 0)
    {
      auto frame = (uintptr_t *)__builtin_alloca (arg_len * WSIZE + 16);
      for (ptrdiff_t i = 0; i < arg_len; ++i)
        frame[i] = this->args[i];

      // Try to convince the compiler to *really* push the args into the stack.
      iret2reg = (uintptr_t)frame;
    }

#define FLT_(ix)   \
  do   \
    {   \
      switch (this->farg_types[ix])   \
        {   \
          case type::float_t:   \
            __asm__ __volatile__ ("ldr s" #ix ", %0" ::   \
                                   "m" (this->fargs[ix].f));   \
            break;   \
          case type::double_t:   \
            __asm__ __volatile__ ("ldr d" #ix ", %0" ::   \
                                   "m" (this->fargs[ix].d));   \
            break;   \
          default:   \
            __asm__ __volatile__ ("ldr q" #ix ", %0" ::   \
                                   "m" (this->fargs[ix].ld));   \
            break;   \
        }   \
    }   \
  while (false)

  // Load floating point values into registers.

  switch (this->fanum)
    {
      default:
        FLT_(7);
      case 7:
        FLT_(6);
      case 6:
        FLT_(5);
      case 5:
        FLT_(4);
      case 4:
        FLT_(3);
      case 3:
        FLT_(2);
      case 2:
        FLT_(1);
      case 1:
        FLT_(0);
      case 0:
        break;
    }

#define fct_args   \
  (uintptr_t, uintptr_t, uintptr_t, uintptr_t,   \
   uintptr_t, uintptr_t, uintptr_t, uintptr_t)

#define call_fct(Type)   \
  ((Type (*) fct_args)this->func)   \
    (I_(0), I_(1), I_(2), I_(3),   \
     I_(4), I_(5), I_(6), I_(7))

#define put_ret(Type)   \
  *(Type *)this->raddr = call_fct (Type)

  if (this->rtype == type::float_t)
    put_ret (float);
  else if (this->rtype == type::double_t)
    put_ret (double);
  else if (this->rtype == type::ldouble_t)
    put_ret (long double);
  else
    {
      intptr_t iret = call_fct (intptr_t);
      intptr_t iret2 = iret2reg;

#undef put_ret
#define put_ret(Type)   \
  *(Type *)this->raddr = (Type)iret;   \
  break

      switch (this->rtype)
        {
          case type::void_t:
            break;
          case type::schar_t:
            put_ret (signed char);
          case type::uchar_t:
            put_ret (unsigned char);
          case type::sshort_t:
            put_ret (short);
          case type::ushort_t:
            put_ret (unsigned short);
          case type::sint_t:
            put_ret (int);
          case type::uint_t:
            put_ret (unsigned int);
          case type::slong_t:
          case type::sllong_t:
            put_ret (long);
          case type::ulong_t:
          case type::ullong_t:
            put_ret (unsigned long);
          case type::struct_t:
            {
              if (!(this->flags & reg_struct_ret))
                break;

              auto wp = (uintptr_t *)
                ((uintptr_t)this->raddr & -(intptr_t)WSIZE);
              auto start = (uintptr_t)this->raddr & (WSIZE - 1);
              auto end = start + this->rsize;

              if (this->rsize <= WSIZE)
                {
                  if (end <= WSIZE)
                    {
                      auto m0 = (2ul << (end * 8 - 1)) - (1ul << (start * 8));
                      wp[0] ^= (wp[0] ^ (iret << (start * 8))) & m0;
                    }
                  else
                    {
                      auto m0 = -(1ul << (start * 8));
                      auto m1 = (2ul << (end * 8 - WSIZE * 8 - 1)) - 1;
                      wp[0] ^= (wp[0] ^ (iret << (start * 8))) & m0;
                      wp[1] ^= (wp[1] ^ (iret >> (WSIZE * 8 - start * 8))) & m1;
                    }
                }
              else
                {
                  auto m0 = -(1ul << (start * 8));
                  wp[0] ^= (wp[0] ^ (iret << (start * 8))) & m0;
                  if (end <= 2 * WSIZE)
                    {
                      auto m1 = (2ul << (end * 8 - WSIZE * 8 - 1)) - 1;
                      wp[1] ^= (wp[1] ^ ((iret >> (WSIZE * 4 - start * 4) >>
                                (WSIZE * 4 - start * 4)) |
                               (iret2 << (start * 8)))) & m1;
                    }
                  else
                    {
                      auto m2 = (2ul << (end * 8 - WSIZE * 16 - 1)) - 1;
                      wp[1] = (iret >> (WSIZE * 8 - start * 8)) |
                                (iret2 << (start * 8));
                      wp[2] ^= (wp[2] ^ (iret2 >>
                                 (WSIZE * 8 - start * 8))) & m2;
                    }
                }

              break;
            }
        }
    }

  return (true);
}

} // namespace cxxcall
